using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Fated.Character.ChartGeneration {
    internal static class ChartGen {
        static double ST, MC, ASD, VX;
        static double[] GL = new double[10], GB = new double[10], HL = new double[10], HB = new double[10];
        static double MOON;
        static double[] RAV = new double[10], X6 = new double[10], Y6 = new double[10], Z6 = new double[10];
        static double[] RASC = new double[14], DECL = new double[14];
        static double RA, Mj, Ms, Mu;
        static double T;
        static double LA;
        static double longitude;
        static double UMTinDays;
        const double PI = 3.141592653589793;
        const double TINY = .000000000000001;
        static double S, A, R, X, Y;
        static int JC;
        static double[,] PL = new double[10, 16];

        public static void generateChart(DateTime d, double[] planets) {
            if (PL[0, 0] < 2) init();

            JULIAN(d.Year, d.Month, d.Day, d.Hour, d.Minute, -8);
            ANGLES(42, 14, true, 121, 47, true);
            CalcPlanets();
            LUNAR();
            PRESENT(planets);
        }

        private static void init() {

            PL[0, 0] = 358.47584;
            PL[0, 1] = 35999.0498;
            PL[0, 2] = -0.00015;
            PL[0, 3] = 0.016751;
            PL[0, 4] = -0.000041;
            PL[0, 5] = 0;
            PL[0, 6] = 1.00000013;
            PL[0, 7] = 101.22083;
            PL[0, 8] = 1.71918;
            PL[0, 9] = 0.00045;
            PL[0, 10] = 0;
            PL[0, 11] = 0;
            PL[0, 12] = 0;
            PL[0, 13] = 0;
            PL[0, 14] = 0;
            PL[0, 15] = 0;
            PL[1, 0] = 102.27938;
            PL[1, 1] = 149472.515;
            PL[1, 2] = 0;
            PL[1, 3] = 0.205614;
            PL[1, 4] = 0.00002;
            PL[1, 5] = 0;
            PL[1, 6] = 0.387098;
            PL[1, 7] = 28.75375;
            PL[1, 8] = 0.37028;
            PL[1, 9] = 0.00012;
            PL[1, 10] = 47.14594;
            PL[1, 11] = 1.1852;
            PL[1, 12] = 0.00017;
            PL[1, 13] = 7.00288;
            PL[1, 14] = 0.00186;
            PL[1, 15] = -0.00001;
            PL[2, 0] = 212.60322;
            PL[2, 1] = 58517.8039;
            PL[2, 2] = 0.00129;
            PL[2, 3] = 0.00682;
            PL[2, 4] = -0.00004;
            PL[2, 5] = 0;
            PL[2, 6] = 0.723332;
            PL[2, 7] = 54.38419;
            PL[2, 8] = 0.50819;
            PL[2, 9] = -0.00139;
            PL[2, 10] = 75.77965;
            PL[2, 11] = 0.89985;
            PL[2, 12] = 0.00041;
            PL[2, 13] = 3.39363;
            PL[2, 14] = 0.001;
            PL[2, 15] = 0;
            PL[3, 0] = 319.5293;
            PL[3, 1] = 19139.8585;
            PL[3, 2] = 0.00018;
            PL[3, 3] = 0.09331;
            PL[3, 4] = 0.00009;
            PL[3, 5] = 0;
            PL[3, 6] = 1.52369;
            PL[3, 7] = 285.43176;
            PL[3, 8] = 1.06977;
            PL[3, 9] = 0.00013;
            PL[3, 10] = 48.78644;
            PL[3, 11] = 0.77099;
            PL[3, 12] = 0;
            PL[3, 13] = 1.85033;
            PL[3, 14] = -0.00068;
            PL[3, 15] = 0.00001;
            PL[4, 0] = 225.32833;
            PL[4, 1] = 3034.69202;
            PL[4, 2] = -0.00072;
            PL[4, 3] = 0.04833;
            PL[4, 4] = 0.00016;
            PL[4, 5] = 0;
            PL[4, 6] = 5.202561;
            PL[4, 7] = 273.27754;
            PL[4, 8] = 0.59943;
            PL[4, 9] = 0.0007;
            PL[4, 10] = 99.44338;
            PL[4, 11] = 1.01053;
            PL[4, 12] = 0.00035;
            PL[4, 13] = 1.30874;
            PL[4, 14] = -0.005696;
            PL[4, 15] = 0;
            PL[5, 0] = 175.46622;
            PL[5, 1] = 1221.55147;
            PL[5, 2] = -0.0005;
            PL[5, 3] = 0.05589;
            PL[5, 4] = -0.00035;
            PL[5, 5] = 0;
            PL[5, 6] = 9.55475;
            PL[5, 7] = 338.30777;
            PL[5, 8] = 1.08522;
            PL[5, 9] = 0.00098;
            PL[5, 10] = 112.79039;
            PL[5, 11] = 0.873195;
            PL[5, 12] = -0.00015;
            PL[5, 13] = 2.49252;
            PL[5, 14] = -0.00392;
            PL[5, 15] = -0.00002;
            PL[6, 0] = 72.64882;
            PL[6, 1] = 428.37911;
            PL[6, 2] = 0.00008;
            PL[6, 3] = 0.046344;
            PL[6, 4] = -0.00003;
            PL[6, 5] = 0;
            PL[6, 6] = 19.21814;
            PL[6, 7] = 98.07155;
            PL[6, 8] = 0.98577;
            PL[6, 9] = -0.00107;
            PL[6, 10] = 73.4771;
            PL[6, 11] = 0.49867;
            PL[6, 12] = 0.00131;
            PL[6, 13] = 0.77246;
            PL[6, 14] = 0.00063;
            PL[6, 15] = 0.00004;
            PL[7, 0] = 37.73067;
            PL[7, 1] = 218.46134;
            PL[7, 2] = -0.00007;
            PL[7, 3] = 0.008997;
            PL[7, 4] = 0;
            PL[7, 5] = 0;
            PL[7, 6] = 30.10957;
            PL[7, 7] = 276.04597;
            PL[7, 8] = 0.32564;
            PL[7, 9] = 0.00014;
            PL[7, 10] = 130.68136;
            PL[7, 11] = 1.09894;
            PL[7, 12] = 0.000249;
            PL[7, 13] = 1.77924;
            PL[7, 14] = -0.00954;
            PL[7, 15] = 0;
            PL[8, 0] = 229.94722;
            PL[8, 1] = 144.91306;
            PL[8, 2] = 0;
            PL[8, 3] = 0.24864;
            PL[8, 4] = 0;
            PL[8, 5] = 0;
            PL[8, 6] = 39.51744;
            PL[8, 7] = 113.52139;
            PL[8, 8] = 0;
            PL[8, 9] = 0;
            PL[8, 10] = 108.95444;
            PL[8, 11] = 1.39601;
            PL[8, 12] = 0.00031;
            PL[8, 13] = 17.14678;
            PL[8, 14] = 0;
            PL[8, 15] = 0;
            PL[9, 0] = 260.75;
            PL[9, 1] = 96.89531;
            PL[9, 2] = 0;
            PL[9, 3] = .0542;
            PL[9, 4] = 0;
            PL[9, 5] = 0;
            PL[9, 6] = 51.72;
            PL[9, 7] = 296.2;
            PL[9, 8] = 0;
            PL[9, 9] = 0;
            PL[9, 10] = 106.37;
            PL[9, 11] = 1.39;
            PL[9, 12] = 0;
            PL[9, 13] = 1.58;
            PL[9, 14] = 0;
            PL[9, 15] = 0;

        }

        private static void JULIAN(int year, int month, int day, int hour, int minute, int gmt) {
            UMTinDays = hour / 24.0 + minute / 1440.0 - gmt / 24.0;

            double JD = 367 * year - (int)(7 * (year + (int)((month + 9) / 12.0)) / 4.0);
            JD = JD + (int)(275 * month / 9.0) + day + 1721013.5;
            JD = JD - 0.5 * Math.Sign(100 * year + month - 190002.5) + 0.5;
            T = (JD - 2415020) + UMTinDays;
            T = T / 36525.0;
            //// ** 1/3/1800 - 28/2/2100 ONLY **
            //// ** EPOCH 1900 JAN 0 AT 12 HRS **
        }

        private static void ANGLES(int latDeg, int latMin, bool isNorth,
            int longDeg, int longMin, bool isWest) {

            int LX = latDeg;
            int LY = latMin;
            LA = RAD(LX + LY / 60.0);
            if (LA == 0) LA = LA + TINY;
            if (!isNorth) LA = -LA;
            //// ** LAT IN RADIANS, SOUTH NEGATIVE **
            int L3 = longDeg;
            int L4 = longMin;
            longitude = L3 + L4 / 60.0;
            if (!isWest) longitude = -longitude;
            //// ** LONG IN DEGREES, EAST NEGATIVE **
            double OB = RAD(23.452294 - 0.0130125 * T);
            RA = (6.6460656 + 2400.0513 * T + .0000258 * T * T + (UMTinDays * 24)) * 15 - longitude;
            while (RA > 360) {
                RA = RA - 360;
            }
            RA = RAD(RA);
            double R1 = RA;
            ST = DEGR(R1) / 15.0;
            MC = Math.Atan(Math.Sin(RA) / (Math.Cos(RA) * Math.Cos(OB)));
            if (MC < 0) MC = MC + PI;
            if (Math.Sin(RA) < 0) MC = MC + PI;
            ASD = Math.Atan(Math.Cos(RA) / -(Math.Sin(OB) * Math.Tan(LA) + Math.Cos(OB) * Math.Sin(RA)));
            if (ASD < 0) ASD = ASD + PI;
            if (Math.Cos(RA) < 0) ASD = ASD + PI;

            double QX = Math.Cos(RA + PI);
            double QY = -Math.Sin(RA + PI) * Math.Cos(OB) - Math.Sin(OB) / Math.Tan(LA);
            if (Math.Abs(QY) < TINY) QY = QY + TINY * Math.Sign(QY);
            if (QY == 0) QY = TINY;
            VX = Math.Atan(QX / QY);
            if (VX < 0) VX = VX + PI;
            if (QX < 0) VX = VX + PI;
            //// ** ANGLES IN MC,ASD,VX IN RADIANS **
        }

        //// 971
        private static void CalcPlanets() {
            Mj = RAD(PL[4, 0] + PL[4, 1] * T + PL[4, 2] * T * T);
            Ms = RAD(PL[5, 0] + PL[5, 1] * T + PL[5, 2] * T * T);
            Mu = RAD(PL[6, 0] + PL[6, 1] * T + PL[6, 2] * T * T);

            double XG, YG, ZG, FF;
            double X9, R9, Y9, EE, AC, XE, YE, ZE, EL, GBL;
            for (int I = 0; I <= 9; I++) {
                JC = -1;
                ASSEMBLE(I);
                double M = FNP(S);
                ASSEMBLE(I);
                double E = DEGR(S);
                double EA = M;
                for (int K = 0; K <= 10; K++) {
                    EA = M + E * Math.Sin(EA);
                }
                if (DEGR(EA) > 179.9999 && DEGR(EA) < 180.0001) FF = EA;
                else { //(DEGR(EA) <= 179.9999 || DEGR(EA) >= 180.0001)
                    double E1 = EA / 2.0;
                    E1 = Math.Tan(E1) * Math.Sqrt((1 + E) / (1 - E));
                    FF = Math.Atan(E1) * 2;
                    if (FF < 0) {
                        FF = FF + 2 * PI;
                    }
                }
                JC = 6;
                double AU = PL[I, JC];
                double RV = AU * (1 - E * Math.Cos(EA));
                RAV[I] = RV;

                ASSEMBLE(I);
                double AP = FNP(S);
                ASSEMBLE(I);
                double AN = FNP(S);
                ASSEMBLE(I);
                double QI = FNP(S);

                X = RV * (Math.Cos(AN) * Math.Cos(AP + FF) - Math.Sin(AN) * Math.Sin(AP + FF) * Math.Cos(QI));
                Y = RV * (Math.Sin(AN) * Math.Cos(AP + FF) + Math.Cos(AN) * Math.Sin(AP + FF) * Math.Cos(QI));
                double Z = RV * Math.Sin(AP + FF) * Math.Sin(QI);
                X6[I] = X;
                Y6[I] = Y;
                Z6[I] = Z;
                if (Y == 0) Y = TINY;
                double TH = Math.Atan(Y / X);
                if (TH < 0) TH = TH + PI;
                if (Y < 0) TH = TH + PI;
                double LT = ASN(Z / RV);
                HL[I] = TH;
                HB[I] = LT;

                if (I == 4) {
                    HL[I] = FNP(HL[I] - RAD(.332) * Math.Sin(2 * Mj - 5 * Ms - RAD(67.6)));
                    HL[I] = FNP(HL[I] - RAD(.056) * Math.Sin(2 * Mj - 2 * Ms + RAD(21)));
                    HL[I] = FNP(HL[I] + RAD(.042) * Math.Sin(3 * Mj - 5 * Ms + RAD(21)));
                    HL[I] = FNP(HL[I] - RAD(.036) * Math.Sin(Mj - 2 * Ms));
                    HL[I] = FNP(HL[I] + RAD(.022) * Math.Cos(Mj - Ms));
                    HL[I] = FNP(HL[I] + RAD(.023) * Math.Sin(2 * Mj - 3 * Ms + RAD(52)));
                    HL[I] = FNP(HL[I] - RAD(.016) * Math.Sin(Mj - 5 * Ms - RAD(69)));

                    X9 = X6[I];
                    Y9 = Y6[I];
                    R9 = Math.Sqrt(X9 * X9 + Y9 * Y9);
                    X6[I] = R9 * Math.Cos(HL[I]);
                    Y6[I] = R9 * Math.Sin(HL[I]);
                }
                if (I == 5) {
                    HL[I] = FNP(HL[I] + RAD(.812) * Math.Sin(2 * Mj - 5 * Ms - RAD(67.6)));
                    HL[I] = FNP(HL[I] - RAD(.229) * Math.Cos(2 * Mj - 4 * Ms - RAD(2)));
                    HL[I] = FNP(HL[I] + RAD(.119) * Math.Sin(Mj - 2 * Ms - RAD(3)));
                    HL[I] = FNP(HL[I] + RAD(.046) * Math.Sin(2 * Mj - 6 * Ms - RAD(69)));
                    HL[I] = FNP(HL[I] + RAD(.014) * Math.Sin(Mj - 3 * Ms + RAD(32)));
                    HB[I] = FNP(HB[I] - RAD(.02) * Math.Cos(2 * Mj - 4 * Ms - RAD(2)));
                    HB[I] = FNP(HB[I] + RAD(.018) * Math.Sin(2 * Mj - 6 * Ms - RAD(49)));

                    X9 = X6[I];
                    Y9 = Y6[I];
                    R9 = Math.Sqrt(X9 * X9 + Y9 * Y9);
                    X6[I] = R9 * Math.Cos(HL[I]);
                    Y6[I] = R9 * Math.Sin(HL[I]);
                }
                if (I == 6) {
                    HL[I] = FNP(HL[I] + RAD(.04) * Math.Sin(Ms - 2 * Mu + RAD(6)));
                    HL[I] = FNP(HL[I] + RAD(.035) * Math.Sin(Ms - 3 * Mu + RAD(33)));
                    HL[I] = FNP(HL[I] - RAD(.015) * Math.Sin(Mj - Mu + RAD(20)));

                    X9 = X6[I];
                    Y9 = Y6[I];
                    R9 = Math.Sqrt(X9 * X9 + Y9 * Y9);
                    X6[I] = R9 * Math.Cos(HL[I]);
                    Y6[I] = R9 * Math.Sin(HL[I]);
                }
                // ***
                if (I == 0) {
                    EE = RV;
                    AC = HL[0];
                }
                X = X6[I];
                Y = Y6[I];
                Z = Z6[I];

                XG = X6[0];
                YG = Y6[0];
                ZG = Z6[0];
                XE = -X6[0];
                YE = -Y6[0];
                ZE = -Z6[0];

                X = X - XG; Y = Y - YG; Z = Z - ZG;
                if (I == 0) {
                    GL[0] = HL[0] + PI;
                    GB[0] = 0;
                    if (GL[0] > 2 * PI) GL[0] = GL[0] - 2 * PI;
                    GL[0] = DEGR(GL[0]);
                }
                if (I > 0) {
                    if (Math.Abs(X) < TINY) {
                        X = TINY * Math.Sign(X);
                        if (X == 0) X = TINY;
                    }
                    if (Math.Abs(Y) < TINY) {
                        Y = TINY * Math.Sign(Y);
                        if (Y == 0) Y = TINY;
                    }
                    EL = Math.Atan(Y / X);
                    if (EL < 0) EL = EL + PI;
                    if (Y < 0) EL = EL + PI;
                    GL[I] = DEGR(EL);
                    GBL = Math.Atan(Z / Math.Sqrt(X * X + Y * Y));
                    GB[I] = DEGR(GBL);
                }
                if (I == 6) GL[I] = FNU(GL[I] - (.85 - .05 * T - .2 * Math.Cos(T * 2 * PI / .84 + .6) + .05 * Math.Cos(T * 2 * PI / .0101 + 2.7)));
                if (I == 7) GL[I] = FNU(GL[I] - (-.55 + .07 * Math.Sin(T * 2 * PI / 1.76)));
                if (I == 8) if (T > .61) GL[I] = FNU(GL[I] - (-(.4 * (T - 1.61) + .4) - .04 * Math.Sin((T - .61) * 2 * PI / 2.48)));
            }
        }

        private static void LUNAR() {

            double LL = 973563 + 1732564379 * T - 4 * T * T;
            double M = 3600;
            double G = 1012395 + 6189 * T;

            double N = 933060.0 - 6962911.0 * T + 7.5 * T * T;

            double G1 = 1203586 + 14648523 * T - 37 * T * T;
            double D = 1262655 + 1602961611 * T - 5 * T * T;

            double L = (LL - G1) / M;
            double L1 = ((LL - D) - G) / M;
            UMTinDays = (LL - N) / M;
            D = D / M;
            Y = 2 * D;

            double ML = 22639.6 * FNZ(L) - 4586.4 * FNZ(L - Y);
            ML = ML + 2369.9 * FNZ(Y) + 769 * FNZ(2 * L) - 669 * FNZ(L1);
            ML = ML - 411.6 * FNZ(2 * UMTinDays) - 212 * FNZ(2 * L - Y);
            ML = ML - 206 * FNZ(L + L1 - Y) + 192 * FNZ(L + Y);
            ML = ML - 165 * FNZ(L1 - Y) + 148 * FNZ(L - L1) - 125 * FNZ(D);
            ML = ML - 110 * FNZ(L + L1) - 55 * FNZ(2 * UMTinDays - Y);
            ML = ML - 45 * FNZ(L + 2 * UMTinDays) + 40 * FNZ(L - 2 * UMTinDays);
            G = FNU((LL + ML) / M);
            MOON = G;
        }

        private static void PRESENT(double[] planets) {

            planets[(int)Planet.PlanetNames.Sun] = GL[0];
            planets[(int)Planet.PlanetNames.Moon] = MOON;
            for (int i = 2; i < 10; i++) {
                planets[i] = GL[i - 1];
            }
            planets[(int)Planet.PlanetNames.Ascendant] = DEGR(ASD);
        }

        public static string DMS(double DEGREE) {
            int DEGREEEE = (int)(DEGREE);
            double REST = FRAC(DEGREE);
            double MINUTES = Math.Round(REST * 60.0);
            string[] ZOD = new string[] { 
                " ARI ", " TAU ", " GEM ", " CAN ", " LEO ", " VIR ", 
                " LIB ", " SCO ", " SAG ", " CAP ", " AQU ", " PIS " };

            int SIGNUM = ((int)(DEGREEEE / 30.0) + 12) % 12;
            string SIGN = ZOD[SIGNUM];
            DEGREEEE = DEGREEEE % 30;
            string DG = PADZERO(DEGREEEE.ToString());
            string MG = PADZERO(MINUTES.ToString());

            return DG + SIGN + MG;

        }

        private static double FRAC(double VA) {
            return VA - (int)(VA);
        }


        private static string PADZERO(string XY) {
            if (XY.Length < 2) return "0" + XY;
            return XY.Substring(XY.Length - 2, 2);
        }


        private static double FNZ(double XY) {
            // ** LUNAR function **
            return Math.Sin(XY * PI / 180.0);
        }

        private static double FNL(double XY) {
            // ** LUNAR function M=3600 **
            return Math.Sign(XY) * ((Math.Abs(XY) / 3600.0) / 360.0 - (int)((Math.Abs(XY) / 3600.0) / 360.0)) * 360;
        }

        private static double FNP(double XY) {
            return XY - (Math.Floor(XY / PI / 2.0) * PI * 2);
        }

        private static double FNU(double XY) {
            return XY - (Math.Floor(XY / 360.0) * 360.0);
        }

        private static double ASN(double XY) {
            return Math.Asin(XY);
        }

        private static double RAD(double XY) {
            return XY * PI / 180.0;
        }

        private static double DEGR(double XY) {
            return XY * 180 / PI;
        }

        private static void ASSEMBLE(int I) {
            double D1 = PL[I, JC + 1];
            double D2 = PL[I, JC + 2];
            double D3 = PL[I, JC + 3];
            JC = JC + 3;
            S = RAD(D1 + D2 * T + D3 * T * T);
        }

        private static void REPO() {
            if (Y == 0) Y = TINY;
            if (X == 0) X = TINY;
            R = Math.Sqrt((X * X + Y * Y));
            A = Math.Atan(Y / X);
            if (A < 0) A = A + PI;
            if (Y < 0) A = A + PI;
        }


    }
}
